/*
XBaseTools
Copyright (C) 2008 Matthew Perry

This library/program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This libary/program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using System.IO;
using BaseTools.IO;
using BaseTools.Configuration;
using BaseTools.Configuration.Parsers;
using BaseTools.Helpers;

namespace SourceTools.IO
{
	/// <summary>
	/// A source file system
	/// </summary>
	public class SourceFileSystem
	{
		private List<SearchPath> searchPaths;
		private string baseDirectory, dgameName;
		
		/// <summary>
		/// Creates a new SourceFileSystem
		/// </summary>
		/// <param name="paths"></param>
		/// <param name="directory"></param>
		/// <param name="gameName"></param>
		public SourceFileSystem(SearchPath[] paths, string directory, string gameName)
		{
			searchPaths = new List<SearchPath>(paths);
			baseDirectory = directory;
			dgameName = gameName;
			FixPaths(gameName);
		}
		
		/// <summary>
		/// Creates a new SourceFileSystem
		/// </summary>
		/// <param name="paths"></param>
		/// <param name="directory"></param>
		/// <param name="gameName"></param>
		public SourceFileSystem(List<SearchPath> paths, string directory, string gameName)
		{
			searchPaths = paths;
			baseDirectory = directory;
			dgameName = gameName;
			FixPaths(gameName);
		}
		
		/// <summary>
		/// This is the list of paths that are searched, if you modify this, you will need to run GeneratePaths() to reorder it in an optimal form
		/// </summary>
		public List<SearchPath> SearchPaths
		{
			get
			{
				return searchPaths;
			}
			set
			{
				searchPaths = value;
			}
		}
		
		/// <summary>
		/// Reorders the SearchPaths so that test paths come first
		/// </summary>
		public void GeneratePaths()
		{
			FixPaths(dgameName);
		}
		
		private void FixPaths(string gameName)
		{
			List<SearchPath> test = new List<SearchPath>(), game = new List<SearchPath>();
			
			foreach(SearchPath pp in searchPaths)
			{
				SearchPath p = pp;
				
				if (p.Path.ToLower() == "|gameinfo_path|.")
					p = new SearchPath(p.Type, gameName);
				else if (p.Path.StartsWith("|all_source_engine_paths|"))
					p = new SearchPath(p.Type, p.Path.Substring(25));
				else if (p.Path.Contains("|"))
					continue;
				
				if (p.Type == SearchPathType.GamePath)
					game.Add(p);
				else
					test.Add(p);
				
				if ((p.Path.ToLower() == gameName.ToLower()) &&
				    new DirectoryInfo(IOHelper.RelativeTo(IOHelper.RelativeTo(baseDirectory, gameName), "addons")).Exists)
				{
					string af = IOHelper.RelativeTo(IOHelper.RelativeTo(baseDirectory, gameName), "addons");
					DirectoryInfo di = new DirectoryInfo(af);
					DirectoryInfo[] addons = di.GetDirectories();
					
					foreach(DirectoryInfo addon in addons)
					{
						string addonPath = IOHelper.RelativeTo(af, addon.Name);
						if (new FileInfo(addonPath + "\\info.txt").Exists)
						{
							ConfigList addonInfo = new SourceConfig().LoadConfiguration(addonPath + "\\info.txt");
							
							if (addonInfo.GetSection("AddonInfo", false) == null)
								continue;
							
							string dov = addonInfo.GetSection("AddonInfo", false).GetString("override", false);
							if (dov == null || dov == "0")
								game.Add(new SearchPath(SearchPathType.GamePath, IOHelper.RelativeTo(gameName, "addons\\" + addon.Name)));
							else
								test.Add(new SearchPath(SearchPathType.TestPath, IOHelper.RelativeTo(gameName, "addons\\" + addon.Name)));
						}
					}
				}
				if ((p.Path.ToLower() == gameName.ToLower()) &&
				    new DirectoryInfo(IOHelper.RelativeTo(IOHelper.RelativeTo(baseDirectory, gameName), "gamemodes")).Exists)
				{
					string af = IOHelper.RelativeTo(IOHelper.RelativeTo(baseDirectory, gameName), "gamemodes");
					DirectoryInfo di = new DirectoryInfo(af);
					DirectoryInfo[] addons = di.GetDirectories();
					
					foreach(DirectoryInfo addon in addons)
					{
						string addonPath = IOHelper.RelativeTo(IOHelper.RelativeTo(af, addon.Name), "content");
						if (new FileInfo(addonPath + "\\info.txt").Exists)
						{
							ConfigList addonInfo = new SourceConfig().LoadConfiguration(addonPath + "\\info.txt");
							
							if (addonInfo.GetSection("AddonInfo", false) == null)
								continue;
							
							string dov = addonInfo.GetSection("AddonInfo", false).GetString("override", false);
							if (dov == null || dov == "0")
								game.Add(new SearchPath(SearchPathType.GamePath, IOHelper.RelativeTo(gameName, addonPath)));
							else
								test.Add(new SearchPath(SearchPathType.TestPath, IOHelper.RelativeTo(gameName, addonPath)));
						}
					}
				}
			}
			
			searchPaths = new List<SearchPath>();
			
			AppendSearch(test);
			AppendSearch(game);
		}
		private void AppendSearch(List<SearchPath> paths)
		{
			foreach(SearchPath p in paths)
			{
				searchPaths.Add(p);
			}
		}
		
		/// <summary>
		/// File information for a file being read from the virtual file system
		/// </summary>
		/// <param name="filename">The virtual filename</param>
		/// <returns>File information for the virtual file</returns>
		public FileInfo ReadInfo(string filename)
		{
			return new FileInfo(RealFilename(filename));
		}
		
		/// <summary>
		/// File information for a file being written into the game directory
		/// </summary>
		/// <param name="filename">The </param>
		/// <returns>File information for the virtual file</returns>
		public FileInfo WriteInfo(string filename)
		{
			return new FileInfo(IOHelper.RelativeTo(IOHelper.RelativeTo(baseDirectory, dgameName), filename));
		}
		
		/// <summary>
		/// Opens a stream for reading
		/// </summary>
		/// <param name="filename">The virtual filename</param>
		/// <returns>The stream to read</returns>
		public StreamReader OpenRead(string filename)
		{
			return File.OpenText(RealFilename(filename));
		}
		
		/// <summary>
		/// Writes text into a file
		/// </summary>
		/// <param name="filename">The virtual filename</param>
		/// <param name="text">The text to write</param>
		public void WriteAllText(string filename, string text)
		{
			File.WriteAllText(IOHelper.RelativeTo(IOHelper.RelativeTo(baseDirectory, dgameName), filename), text);
		}
		
		/// <summary>
		/// Reads text from a file
		/// </summary>
		/// <param name="filename">The virtual filename</param>
		/// <returns>The contents of the file</returns>
		public string ReadAllText(string filename)
		{
			return File.ReadAllText(RealFilename(filename));
		}
		
		/// <summary>
		/// Checks if a virtual file exists
		/// </summary>
		/// <param name="filename">The virtual filename</param>
		/// <returns>Whether the virtual file exists</returns>
		public bool FileExists(string filename)
		{
			foreach(SearchPath p in searchPaths)
			{
				string sp = RelBase(p.Path, filename);
				if (new FileInfo(sp).Exists || new DirectoryInfo(sp).Exists)
					return true;
			}
			
			return false;
		}
		
		/// <summary>
		/// Converts a virtual filename to a real filename
		/// </summary>
		/// <param name="filename">The virtual filename</param>
		/// <returns>The real filename</returns>
		public string RealFilename(string filename)
		{
			foreach(SearchPath p in searchPaths)
			{
				string sp = RelBase(p.Path, filename);
				if (new FileInfo(sp).Exists || new DirectoryInfo(sp).Exists)
					return sp;
			}
			
			return null;
		}
		
		
		
		/// <summary>
		/// List files in a directory
		/// </summary>
		/// <param name="directory">The virtual directory</param>
		/// <returns>A list of files in the directory</returns>
		public List<string> ListFiles(string directory)
		{
			return ListFiles(directory, "*");
		}
		
		/// <summary>
		/// List files in a directory
		/// </summary>
		/// <param name="directory">The virtual directory</param>
		/// <param name="filter">The filename filter</param>
		/// <returns>A list of files in the directory</returns>
		public List<string> ListFiles(string directory, string filter)
		{
			List<string> fileList = new List<string>();
			SortedList<string, bool> ci = new SortedList<string, bool>();
			
			foreach(SearchPath p in searchPaths)
			{
				DirectoryInfo di;
				
				if (IOHelper.PathPosition(p.Path) == PathInfo.Relative)
					di = new DirectoryInfo(IOHelper.RelativeTo(IOHelper.RelativeTo(baseDirectory, p.Path), directory));
				else
					di = new DirectoryInfo(IOHelper.RelativeTo(p.Path, directory));
				
				if (!di.Exists)
					continue;
				
				FileInfo[] fi;
				fi = di.GetFiles(filter);
				
				foreach(FileInfo f in fi)
				{
					if (!ci.ContainsKey(f.Name.ToLower()))
					{
						ci.Add(f.Name.ToLower(), false);
						fileList.Add(f.Name);
					}
				}
			}
			
			return fileList;
		}
		
		/// <summary>
		/// List directories
		/// </summary>
		/// <param name="directory">The virtual directory to search in</param>
		/// <returns>A list of directories in the virtual directory</returns>
		public List<string> ListDirectories(string directory)
		{
			List<string> fileList = new List<string>();
			SortedList<string, bool> ci = new SortedList<string, bool>();
			
			foreach(SearchPath p in searchPaths)
			{
				DirectoryInfo di;
				
				if (IOHelper.PathPosition(p.Path) == PathInfo.Relative)
					di = new DirectoryInfo(IOHelper.RelativeTo(IOHelper.RelativeTo(baseDirectory, p.Path), directory));
				else
					di = new DirectoryInfo(IOHelper.RelativeTo(p.Path, directory));
				
				if (!di.Exists)
					continue;
				
				DirectoryInfo[] fi;
				fi = di.GetDirectories();
				
				foreach(DirectoryInfo f in fi)
				{
					if (!ci.ContainsKey(f.Name.ToLower()))
					{
						ci.Add(f.Name.ToLower(), false);
						fileList.Add(f.Name);
					}
				}
			}
			
			return fileList;
		}
		
		/// <summary>
		/// List files in a directory recursively
		/// </summary>
		/// <param name="directory">The virtual directory</param>
		/// <returns>A list of files in the directory</returns>
		public List<string> ListFilesRecursive(string directory)
		{
			return ListFilesRecursive(directory, "*", false);
		}
		
		/// <summary>
		/// List files in a directory recursively
		/// </summary>
		/// <param name="directory">The virtual directory</param>
		/// <param name="filter">The filename filter</param>
		/// <param name="prefix">Whether the directory should be prefixed before the files</param>
		/// <returns>A list of files in the directory</returns>
		public List<string> ListFilesRecursive(string directory, string filter, bool prefix)
		{
			List<string> lst = new List<string>();
			
			ListFilesRecursive(lst, directory, prefix ? directory + "\\" : "", filter);
			
			return lst;
		}
		
		/// <summary>
		/// List directories in a directory recursively
		/// </summary>
		/// <param name="directory">The virtual directory</param>
		/// <returns>A list of directories in the directory</returns>
		public List<string> ListDirectoriesRecursive(string directory)
		{
			return ListDirectoriesRecursive(directory, false);
		}
		
		/// <summary>
		/// List directories in a directory recursively
		/// </summary>
		/// <param name="directory">The virtual directory</param>
		/// <param name="prefix">Whether the directory should be prefixed before the names</param>
		/// <returns>A list of directories in the directory</returns>
		public List<string> ListDirectoriesRecursive(string directory, bool prefix)
		{
			List<string> lst = new List<string>();
			
			ListDirectoriesRecursive(lst, directory, prefix ? directory + "\\" : "");
			
			return lst;
		}
		
		private void ListDirectoriesRecursive(List<string> lst, string directory, string prefix)
		{
			List<string> lDir = new List<string>();
			
			lDir = ListDirectories(directory);
			
			foreach(string dfn in lDir)
			{
				lst.Add(IOHelper.RelativeTo(prefix, dfn));
				ListDirectoriesRecursive(lst, IOHelper.RelativeTo(directory, dfn), IOHelper.RelativeTo(prefix, dfn));
			}
		}
		
		private void ListFilesRecursive(List<string> lst, string directory, string prefix, string filter)
		{
			List<string> lFile = new List<string>(), lDir = new List<string>();
			
			lFile = ListFiles(directory, filter);
			
			foreach(string fn in lFile)
			{
				lst.Add(IOHelper.RelativeTo(prefix, fn));
			}
			
			lDir = ListDirectories(directory);
			
			foreach(string dfn in lDir)
			{
				ListFilesRecursive(lst, IOHelper.RelativeTo(directory, dfn), IOHelper.RelativeTo(prefix, dfn), filter);
			}
		}
		
		/// <summary>
		/// Base directory for hl2.exe
		/// </summary>
		public string BaseDirectory
		{
			get
			{
				return baseDirectory;
			}
		}
		
		/// <summary>
		/// Game name
		/// </summary>
		public string GameName
		{
			get
			{
				return dgameName;
			}
		}
		
		private string RelBase(string p, string f)
		{
			return IOHelper.RelativeTo(IOHelper.RelativeTo(baseDirectory, p), f);
		}
		
		private static string GetGameDir(string directory, string gamedirectory)
		{
			directory = IOHelper.CleanPath(directory);
			if (IOHelper.PathPosition(gamedirectory) == PathInfo.Relative && directory.Length > 0)
				return directory + "\\" + gamedirectory;
			else
				return gamedirectory;
		}
	}
}
